/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.draw.gui;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.cburch.draw.canvas.Selection;
import com.cburch.draw.canvas.SelectionEvent;
import com.cburch.draw.canvas.SelectionListener;
import com.cburch.draw.model.CanvasObject;
import com.cburch.logisim.data.AbstractAttributeSet;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeEvent;
import com.cburch.logisim.data.AttributeListener;
import com.cburch.logisim.data.AttributeSet;

public class SelectionAttributes extends AbstractAttributeSet {
    private class Listener implements SelectionListener, AttributeListener {
        //
        // SelectionListener
        //
        @Override
        public void selectionChanged(SelectionEvent ex) {
            Map<AttributeSet, CanvasObject> oldSel = selected;
            Map<AttributeSet, CanvasObject> newSel = new HashMap<AttributeSet, CanvasObject>();
            for (CanvasObject o : selection.getSelected()) {
                newSel.put(o.getAttributeSet(), o);
            }
            selected = newSel;
            boolean change = false;
            for (AttributeSet attrs : oldSel.keySet()) {
                if (!newSel.containsKey(attrs)) {
                    change = true;
                    attrs.removeAttributeListener(this);
                }
            }
            for (AttributeSet attrs : newSel.keySet()) {
                if (!oldSel.containsKey(attrs)) {
                    change = true;
                    attrs.addAttributeListener(this);
                }
            }
            if (change) {
                computeAttributeList(newSel.keySet());
                fireAttributeListChanged();
            }
        }

        private void computeAttributeList(Set<AttributeSet> attrsSet) {
            Set<Attribute<?>> attrSet = new LinkedHashSet<Attribute<?>>();
            Iterator<AttributeSet> sit = attrsSet.iterator();
            if (sit.hasNext()) {
                AttributeSet first = sit.next();
                attrSet.addAll(first.getAttributes());
                while (sit.hasNext()) {
                    AttributeSet next = sit.next();
                    for (Iterator<Attribute<?>> ait = attrSet.iterator(); ait.hasNext(); ) {
                        Attribute<?> attr = ait.next();
                        if (!next.containsAttribute(attr)) {
                            ait.remove();
                        }
                    }
                }
            }

            Attribute<?>[] attrs = new Attribute[attrSet.size()];
            Object[] values = new Object[attrs.length];
            int i = 0;
            for (Attribute<?> attr : attrSet) {
                attrs[i] = attr;
                values[i] = getSelectionValue(attr, attrsSet);
                i++;
            }
            SelectionAttributes.this.selAttrs = attrs;
            SelectionAttributes.this.selValues = values;
            SelectionAttributes.this.attrsView
                = Collections.unmodifiableList(Arrays.asList(attrs));
            fireAttributeListChanged();
        }

        //
        // AttributeSet listener
        //
        @Override
        public void attributeListChanged(AttributeEvent e) {
            // show selection attributes
            computeAttributeList(selected.keySet());
        }

        @Override
        public void attributeValueChanged(AttributeEvent e) {
            if (selected.containsKey(e.getSource())) {
                @SuppressWarnings("unchecked")
                Attribute<Object> attr = (Attribute<Object>) e.getAttribute();
                Attribute<?>[] attrs = SelectionAttributes.this.selAttrs;
                Object[] values = SelectionAttributes.this.selValues;
                for (int i = 0; i < attrs.length; i++) {
                    if (attrs[i] == attr) {
                        values[i] = getSelectionValue(attr, selected.keySet());
                    }
                }
            }
        }
    }

    private Selection selection;
    private Listener listener;
    private Map<AttributeSet, CanvasObject> selected;
    private Attribute<?>[] selAttrs;
    private Object[] selValues;
    private List<Attribute<?>> attrsView;

    public SelectionAttributes(Selection selection) {
        this.selection = selection;
        this.listener = new Listener();
        this.selected = Collections.emptyMap();
        this.selAttrs = new Attribute<?>[0];
        this.selValues = new Object[0];
        this.attrsView = Collections.unmodifiableList(Arrays.asList(selAttrs));

        selection.addSelectionListener(listener);
        listener.selectionChanged(null);
    }

    public Iterable<Map.Entry<AttributeSet, CanvasObject>> entries() {
        Set<Map.Entry<AttributeSet, CanvasObject>> raw = selected.entrySet();
        List<Map.Entry<AttributeSet, CanvasObject>> ret;
        ret = new ArrayList<Map.Entry<AttributeSet, CanvasObject>>(raw);
        return ret;
    }

    //
    // AbstractAttributeSet methods
    //
    @Override
    protected void copyInto(AbstractAttributeSet dest) {
        listener = new Listener();
        selection.addSelectionListener(listener);
    }

    @Override
    public List<Attribute<?>> getAttributes() {
        return attrsView;
    }

    @SuppressWarnings("unchecked")
	@Override
    public <V> V getValue(Attribute<V> attr) {
        Attribute<?>[] attrs = this.selAttrs;
        Object[] values = this.selValues;
        for (int i = 0; i < attrs.length; i++) {
            if (attrs[i] == attr) {
                return (V) values[i];
            }
        }
        return null;
    }

    @Override
    public <V> void setValue(Attribute<V> attr, V value) {
        Attribute<?>[] attrs = this.selAttrs;
        Object[] values = this.selValues;
        for (int i = 0; i < attrs.length; i++) {
            if (attrs[i] == attr) {
                boolean same = value == null ? values[i] == null : value.equals(values[i]);
                if (!same) {
                    values[i] = value;
                    for (AttributeSet objAttrs : selected.keySet()) {
                        objAttrs.setValue(attr, value);
                    }
                }
                break;
            }
        }
    }

    private static Object getSelectionValue(Attribute<?> attr,
            Set<AttributeSet> sel) {
        Object ret = null;
        for (AttributeSet attrs : sel) {
            if (attrs.containsAttribute(attr)) {
                Object val = attrs.getValue(attr);
                if (ret == null) {
                    ret = val;
                } else {
                    return null;
                }
            }
        }
        return ret;
    }
}
